package com.fxu.framework.biz.api;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fxu.framework.biz.base.Param;
import com.fxu.framework.biz.rest.ApiBizRest;
import com.fxu.framework.biz.rest.ApiProxyRest;
import com.fxu.framework.core.base.Cascade;
import com.fxu.framework.core.base.Constant;
import com.fxu.framework.core.base.Result;
import com.fxu.framework.core.exception.MsgException;
import com.fxu.framework.core.holder.SpringHolder;
import com.fxu.framework.core.sql.SEntity;
import com.fxu.framework.core.sql.SQuery;
import com.fxu.framework.core.util.BeanUtil;
import com.fxu.framework.core.util.FieldUtil;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.validation.Valid;
import javax.validation.constraints.*;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/***
 * <p>xfs</p>
 * Vue对应通用控制层
 * @author fangxu
 * @version 6.0.0
 ***/
@Slf4j
public abstract class ApiBizController {
	// 没有实现XXXRestImpl，不能保存数据
	@Value("${api-biz-save-not-rest-impl:true}")
	private Boolean saveNotRestImpl;

	// 返回各自项目ApiProxyRest实现
	protected abstract ApiProxyRest apiNewImpl();

	// 通用处理搜索参数
	protected <SE> Result<Map<String, Object>> listBefore(Class<SE> clazz, Map<String, Object> params) {
		return Result.ok(params);
	}

	// 通用处理保存内容
	protected <SE> Result<SE> saveBefore(SE entity) {
		return Result.ok(entity);
	}

	protected final String thisClassName = this.getClass().getName().replace(this.getClass().getSimpleName(), "%s");
	/** 类缓存 */
	private final static Map<String, Class<?>> CLASS_MAP = new ConcurrentHashMap<>();
	/** 服务类缓存 */
	private final static Map<Class<?>, ApiBizRest<?>> SERVICE_MAP = new ConcurrentHashMap<>();

	/**
	 * 根据传入的类别名，找到相应的类
	 * @param bean 传入的别名[userRole=>UserRole]
	 * @return UserRole.class
	 */
	@SuppressWarnings("unchecked")
	private <SE> Class<SE> getBeanClass(String bean) {
		if (StrUtil.isEmpty(bean)) {
			log.error("入参错误,bean is empty!");
			throw new MsgException("入参错误,bean is empty!");
		}
		Class<?> clazz = CLASS_MAP.get(bean);
		if (null != clazz) {
			return (Class<SE>) clazz;
		}

		String temp = thisClassName;
		String[] array = StrUtil.splitToArray(temp, ".");
		array[array.length - 2] = "entity";
		if (bean.contains(".")) {
			array[array.length - 3] = bean.substring(0, bean.indexOf("."));
			bean = bean.substring(bean.indexOf(".") + 1);
		}
		temp = CollUtil.join(CollUtil.toList(array), ".");
		String className = String.format(temp, StrUtil.upperFirst(bean));
		try {
			// Entity类实例化
			ClassLoader loader = Thread.currentThread().getContextClassLoader();
			clazz = Class.forName(className, true, loader);
		} catch (ClassNotFoundException e) {
			log.error("class not find! bean={}, class={}", bean, className, e);
			throw new MsgException("bean=" + bean + ",class not find=" + className);
		}
		CLASS_MAP.put(bean, clazz);
		return (Class<SE>) clazz;
	}

	/**
	 * 找到对应的处理类，但要注意文件地址和命名
	 * @param <T> 本类范型
	 * @param clazz Entity实体类
	 * @return ApiBizRest 对象
	 */
	@SuppressWarnings("unchecked")
	public static <T extends SEntity<?>> ApiBizRest<T> getApiBaseRest(Class<T> clazz) {
		if (SERVICE_MAP.containsKey(clazz)) {
			return (ApiBizRest<T>) SERVICE_MAP.get(clazz);
		}
		//TODO 这个RestImpl的名字要注意了EntityRestImpl => XxxRestImpl => TestRestImpl
		String clazzName = StrUtil.lowerFirst(clazz.getSimpleName()) + "RestImpl";
		if (SpringHolder.hasBean(clazzName)) {
			ApiBizRest<T> result = SpringHolder.getBean(clazzName);
			SERVICE_MAP.put(clazz, result);
			return result;
		} else {
			//logger.debug("hasBean({})={}", clazzName, SpringHolder.hasBean(clazzName));
			return null;
		}
	}

	@ResponseBody
	@PostMapping(value = "/listPageBy/{bean}/", consumes = "application/json", produces = "application/json")
	@ApiOperation("读取分页列表")
	@ApiImplicitParams({
		@ApiImplicitParam(name = "bean", value = "类名", required = true, paramType = "path", dataTypeClass = String.class),
		@ApiImplicitParam(name = "map", value = "{\"" + Constant.PAGE_NUM + "\":1,\"" + Constant.PAGE_SIZE
					+ "\":10,\"" + Constant.SHOW_FLAGS + "\":\"\",\"" + Constant.PARAMS
				 	+ "\":{\"id\":\"1\",\"xx$Lk\":\"*\",\"xx$Gt\":\"*\"}}",
					required = true, paramType = "body", dataTypeClass = Map.class)
	})
	public <SE extends SEntity<SE>> Result<List<SE>> listPageBy(@PathVariable("bean") String bean, @RequestBody @Valid Map<String, Object> map) {
		return listResultBy(bean, map, true);
	}

	@ResponseBody
	@PostMapping(value = "/offsetBy/{bean}/", consumes = "application/json", produces = "application/json")
	@ApiOperation("根据nextId读取列表")
	@ApiImplicitParams({
			@ApiImplicitParam(name = "bean", value = "类名", required = true, paramType = "path", dataTypeClass = String.class),
			@ApiImplicitParam(name = "map", value = "{\"" + Constant.NEXT_ID + "\":999999999,\"" + Constant.PAGE_SIZE
					+ "\":10,\"" + Constant.SHOW_FLAGS + "\":\"\",\"" + Constant.PARAMS
					+ "\":{\"id\":\"1\",\"xx$Lk\":\"*\",\"xx$Gt\":\"*\"}}",
					required = true, paramType = "body", dataTypeClass = Map.class)
	})
	public <SE extends SEntity<SE>> Result<List<SE>> offsetBy(@PathVariable("bean") String bean, @RequestBody @Valid Map<String, Object> map) {
		return listResultBy(bean, map, false);
	}

	public <SE extends SEntity<SE>> Result<List<SE>> listResultBy(String bean, Map<String, Object> map, boolean pageBy) {
		if (StrUtil.isEmpty(bean)) {
			return Result.error("参数错误!");
		}
		Class<SE> clazz = getBeanClass(bean);
		ApiBizRest<SE> apiBaseRest = getApiBaseRest(clazz);
		@SuppressWarnings("unchecked")
		Map<String, Object> params = map.containsKey(Constant.PARAMS) ?
				(Map<String, Object>) map.get(Constant.PARAMS) : new HashMap<>();
		List<String> nullKeys = new ArrayList<>();
		for (String key : params.keySet()) {
			if (params.get(key) == null) nullKeys.add(key);
		}
		for (String nullKey : nullKeys) {
			params.remove(nullKey);
		}
		Map<String, Object> mapCopy = MapUtil.newConcurrentHashMap(params);
		// 统一处理入参
		Result<SQuery<SE>> ret = null;
		Result<Map<String, Object>> result = listBefore(clazz, params);
		if (!result.isSuccess()) {
			log.error("listBefore error!result={}", result);
			return Result.error(result.getMsg());
		}
		map.put(Constant.PARAMS, result.getMapData());
		params = result.getMapData();
		if (apiBaseRest != null) {
			// 直接入参数为params, SQuery, 返回 SQuery
			ret = apiBaseRest.filterMap(clazz, params);
			if (!ret.isSuccess()) {
				log.error("fillMap error!result={}", ret);
				return Result.error(ret.getMsg());
			}
		}
		Param<SE> param = new Param<>(clazz, map, ret == null ? null : ret.getData(), true);

		ApiProxyRest apiProxyRest = apiNewImpl();
		Page<SE> pageInfo = null; Long count = null; List<SE> list;
		if (pageBy) {
			pageInfo = apiProxyRest.pageBy(param.getPage(), param.getSize(), param.getSQuery());
			list = pageInfo.getRecords();
		} else {
			list = apiProxyRest.offsetBy(param.getSize(), param.getNextId(), param.getSQuery());
			// countBy会改变query的notAnyOrder值，所以放offsetBy下面执行
			count = param.getNextId() == null && !param.isNoCount() ? apiProxyRest.countBy(param.getSQuery()) : null;
		}

		if (list != null && list.size() > 0) {
			List<String> showFlagList = param.getShowFlag();
			// 根据条件[showFlags]填充字段
			if (apiBaseRest != null && !CollUtil.isEmpty(showFlagList)) {
				apiBaseRest.fillBeans(list, showFlagList);
				apiBaseRest.fillBeans(list, showFlagList, mapCopy);
			}
		}
		return pageBy ? Result.ok(pageInfo) : Result.ok(list, count, FieldUtil.getPriKeyName(clazz));
	}

	@ResponseBody
	@DSTransactional
	@ApiOperation("批量保存")
	@PostMapping(value = "/saveBatch/{bean}/", consumes = "application/json", produces = "application/json")
	@ApiImplicitParams({
		@ApiImplicitParam(name = "bean", value = "类名", required = true, paramType = "path", dataTypeClass = String.class),
		@ApiImplicitParam(name = "beans", value = "对象集合", required = true, paramType = "body", dataTypeClass = List.class)
	})
	public <SE extends SEntity<SE>> Result<String> saveBatch(@PathVariable("bean") String bean, @RequestBody @Valid List<Map<String, Object>> beans) {
		if (StrUtil.isEmpty(bean) || beans == null || beans.size() <= 0) {
			return Result.error("参数错误!");
		}
		Class<SE> clazz = getBeanClass(bean);
		ApiBizRest<SE> apiBaseRest = getApiBaseRest(clazz);
		if (!saveNotRestImpl && apiBaseRest == null) {
			return Result.error("没有实现[" + clazz.getSimpleName() + "RestImpl]不能修改数据!");
		}

		SE dbBean; Result<SE> result;
		Map<Object, SE> dbBeans = new HashMap<>();
		List<SE> list = BeanUtil.mapListToBean(beans, clazz, true);
		for (SE entity : list) {
			if (entity == null) {
				return Result.error("参数错误!传入对象列表中有对象为null!");
			}
			// 统一处理保存对象
			result = saveBefore(entity);
			if (!result.isSuccess()) {
				log.error("listBefore error!result={}", result);
				return Result.error(result.getMsg());
			}
			entity = result.getData();
			// saveBefore切入
			if (apiBaseRest != null) {
				Object idVal = FieldUtil.getPriKeyFieldVal(entity);
				dbBean = idVal == null ? null : apiNewImpl().findById(clazz, (Serializable) idVal);
				if (idVal != null) dbBeans.put(idVal, dbBean);
				result = apiBaseRest.saveBefore(dbBean, entity);
				if (!result.isSuccess()) {
					log.error("saveBefore error!result={}", result);
					return Result.error(result.getMsg());
				}
			}
			// @Valid 验证字段 @Size @NotNull @Min @Max @NotBlank @Digits
			String checkResult = checkValid(clazz, entity);
			if (!StrUtil.isEmpty(checkResult)) {
				return Result.error(checkResult);
			}
		}
		for (SE entity : list) {
			// 保存单个, 为了保证数据一致性, 抛出异常
			boolean back = apiNewImpl().saveOne(entity);
			if (!back) {
				throw new MsgException("数据保存出错了, 请联系管理员!");
			}
			// saveAfter切入, 为了保证数据一致性, 抛出异常
			if (apiBaseRest != null) {
				dbBean = dbBeans.get(FieldUtil.getPriKeyFieldVal(entity));
				result = apiBaseRest.saveAfter(dbBean, entity);
				if (!result.isSuccess()) {
					throw new MsgException(result.getMsg());
				}
			}
		}
        return Result.ok("成功");
	}

	// 验证Entity字段注解是否满足条件
	@SuppressWarnings("unchecked")
	private <T> String checkValid(Class<T> clazz, @Valid T entity) {
		Object idVal = FieldUtil.getPriKeyFieldVal(entity);
		if (!com.baomidou.mybatisplus.core.toolkit.StringUtils.checkValNull(idVal)) {
			// 编辑不校验，允许只更新几个字段
			return null;
		}
		for (Field field : ReflectUtil.getFields(clazz)) {
			Object value = ReflectUtil.getFieldValue(entity, field);
			String checkBack = checkValid(entity, field, value);
			if (StrUtil.isNotEmpty(checkBack)) {
				return checkBack;
			}
			// 校验级联子对象
			Cascade cascade = field.getAnnotation(Cascade.class);
			if (cascade != null && value != null) {
				Class<?> linkClazz = List.class == field.getType() ? FieldUtil.getFieldClass(field) : field.getType();
				List<Object> newList = new ArrayList<>();
				if (List.class == field.getType()) {
					newList = (List<Object>) value;
				} else {
					newList.add(value);
				}
				for (Object child : newList) {
					for (Field childField : ReflectUtil.getFields(linkClazz)) {
						// 关联字段不需要判断是否为null，因为会赋值
						if (!childField.getName().equals(cascade.linkField())) {
							String childCheckBack = checkValid(child, childField, null);
							if (StrUtil.isNotEmpty(childCheckBack)) {
								return childCheckBack;
							}
						}
					}
				}
			}
		}
		return null;
	}

	// 验证字段注解是否满足条件
	private String checkValid(Object entity, Field field, Object value) {
		if (value == null) {
			value = ReflectUtil.getFieldValue(entity, field);
		}
		String strVal = value == null ? null : value.toString();
		NotNull notNull = field.getAnnotation(NotNull.class);
		if (notNull != null && value == null) {
			return notNull.message();
		}
		NotBlank notBlank = field.getAnnotation(NotBlank.class);
		if (notBlank != null && StrUtil.isBlank(strVal)) {
			return notBlank.message();
		}
		Size size = field.getAnnotation(Size.class);
		if (size != null && strVal != null && ((size.min() > 0 && strVal.length() < size.min())
				|| (size.max() > 0 && strVal.length() > size.max()))) {
			return size.message();
		}
		Min min = field.getAnnotation(Min.class);
		if (min != null && (strVal == null || (NumberUtil.isLong(strVal) && Long.parseLong(strVal) < min.value()))) {
			return min.message();
		}
		Max max = field.getAnnotation(Max.class);
		if (max != null && (strVal == null || (NumberUtil.isLong(strVal) && Long.parseLong(strVal) > max.value()))) {
			return max.message();
		}
		//TODO @Digits @DecimalMin @DecimalMax
		return null;
	}
	
//	@ResponseBody
//	@PostMapping(value = "/deleteBatch/{bean}/", consumes = "application/json", produces = "application/json")
//	@ApiOperation("批量删除")
//	@ApiImplicitParams({
//		@ApiImplicitParam(name = "bean", value = "类名", required = true, paramType = "path", dataTypeClass = String.class),
//		@ApiImplicitParam(name = "ids", value = "ID集合", required = true, paramType = "body", dataTypeClass = List.class)
//	})
//	public Result deleteBatch(@PathVariable("bean") String bean, @RequestBody @Valid List<String> ids) {
//		if (StrUtil.isEmpty(bean) || ids == null || ids.size() <= 0) {
//			return Result.error("参数错误!");
//		}
//        return Result.ok(apiNewImpl().deleteByIds(getBeanClass(bean), ids));
//	}
}